-
Notifications
You must be signed in to change notification settings - Fork 93
[DevTools] PR2: Backend Services, Console Viewer, and Resources #2879
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[DevTools] PR2: Backend Services, Console Viewer, and Resources #2879
Conversation
🦋 Changeset detectedLatest commit: f55e43c The changes in this PR will be included in the next version bump. This PR includes changesets to release 7 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since it's quite a big PR, I've reviewed the backend pieces at a high level and left feedback. I'll take a pass at frontend components later.
package.json
Outdated
"@aws-sdk/client-cloudformation": "^3.828.0", | ||
"@aws-sdk/client-cloudwatch-logs": "^3.835.0", | ||
"@aws-sdk/client-cognito-identity-provider": "^3.750.0", | ||
"@aws-sdk/client-dynamodb": "^3.750.0", | ||
"@aws-sdk/client-iam": "^3.750.0", | ||
"@aws-sdk/client-iam": "^3.835.0", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need aws-sdk upgrade as part of this? If not, we should decouple.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
reverting.
package.json
Outdated
"dependencies": { | ||
"@aws-sdk/client-lambda": "^3.835.0", | ||
"cookie-signature": "1.0.6", | ||
"debounce-promise": "^3.1.2", | ||
"esbuild": "0.25.5", | ||
"express": "^5.1.0", | ||
"express-rate-limit": "^7.5.0", | ||
"jszip": "^3.10.1", | ||
"ws": "^8.18.2" | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need all these dependencies, and in the root package?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope. Removing
* Attempts to start the server on the specified port | ||
* @param server The HTTP server | ||
* @param port The port to use | ||
* @returns A promise that resolves with the port when the server starts | ||
* @throws Error if the port is already in use | ||
*/ | ||
async findAvailablePort( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The method name is not matching the documentation and what it is doing. And why are we starting a server in PortChecker
class?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
moving the server starting to devtools command -- this is a relic from when we tried multiple ports.
const cleanAnsiCodes = (text: string): string => { | ||
// This regex handles various ANSI escape sequences including colors, bold, dim, etc. | ||
return text.replace(/\u001b\[\d+(;\d+)*m|\[2m|\[22m|\[1m|\[36m|\[39m/g, ''); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Look into stripANSI
which is already used in this repo.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have we already discussed keeping the styles offline with something like ansi-to-html
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
switched to stripANSI for now-- is there a benefit to taking it offline if stripAnsi already exists in the repo?
* @param constructPath The CDK construct path | ||
* @returns A normalized construct path | ||
*/ | ||
const normalizeCDKConstructPath = (constructPath: string): string => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems duplicated from here
Line 237 in 146f603
private normalizeCDKConstructPath = (constructPath: string): string => { |
We should see if we can refactor cfn_deployment_progress_logger.ts
such that it can be used for both CLI and DevConsole instead of duplicating the logic
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pulled that function out and exported it to get rid of duplication.
if (sandboxState === 'unknown') { | ||
try { | ||
// Check AWS stack state | ||
const cfnClient = new CloudFormationClient(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All of these clients should be injected at runtime. Will make the unit testing much easier and simpler.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done. this does make unit testing a lot easier! I'll go ahead and make unit tests more robust in a separate commit
await cfnClient.send( | ||
new DescribeStacksCommand({ StackName: stackName }), | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are not using the response here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we're just checking if the stack exists. but maybe using exceptions for control flow is not appropriate. Would it be better if I used ListStacksCommand or validateTemplate. Or waitUntilStackExists with a short timeout?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can't the service return undefined as well that we should check against? https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/Package/-aws-sdk-client-cloudformation/Interface/DescribeStacksCommandOutput/
// Override printer.log to capture the log level | ||
printer.log = function (message: string, level?: LogLevel) { | ||
currentLogLevel = level ? level : LogLevel.DEBUG; | ||
const result = originalLog.call(this, message, currentLogLevel); | ||
currentLogLevel = null; | ||
return result; | ||
}; | ||
|
||
// Override printer.print (the lower-level method) | ||
printer.print = function (message: string) { | ||
// Call the original print method | ||
originalPrint.call(this, message); | ||
// Avoid double processing if this is called from printer.log | ||
if (processingMessage) { | ||
return; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should avoid doing this. This creates sideeffects in other parts of code that would be very hard to debug/troubleshoot
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
refactored to pass in my own printer. required taking sandboxfactory out of the injected dependencies -- let me know what you think about the approach I just pushed.
// Get region information | ||
const cfnClient = new CloudFormationClient(); | ||
const regionValue = cfnClient.config.region; | ||
let region = null; | ||
|
||
try { | ||
if (typeof regionValue === 'function') { | ||
if (regionValue.constructor.name === 'AsyncFunction') { | ||
region = await regionValue(); | ||
} else { | ||
region = regionValue(); | ||
} | ||
} else if (regionValue) { | ||
region = String(regionValue); | ||
} | ||
|
||
// Final check to ensure region is a string | ||
if (region && typeof region !== 'string') { | ||
region = String(region); | ||
} | ||
} catch (error) { | ||
printer.log('Error processing region: ' + error, LogLevel.ERROR); | ||
region = null; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See if we can reuse this
export class RegionFetcher { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yep, this also makes testing easy. Adding it.
/** | ||
* Service for handling socket events | ||
*/ | ||
export class SocketHandlerService { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This class also needs required unit test coverage
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yep, I hadn't put it in this PR. updated and added.
…nd added to dependencies
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't say I did a full review here — there's a lot here. 🎉 But, I left a handful of comments for you for things that jumped out at me.
My other big concern across the board is testing. Given that this is destined for a feature branch, and given that it's already huge, I could be convinced to see a separate PR focused on e2e testing. Some lower level unit/interaction testing would be good too. But, long term, before I'd put this in front of customers, I'd want to know I "can't" break it. And, I think e2e's are how I gain that confidence.
const cleanAnsiCodes = (text: string): string => { | ||
// This regex handles various ANSI escape sequences including colors, bold, dim, etc. | ||
return text.replace(/\u001b\[\d+(;\d+)*m|\[2m|\[22m|\[1m|\[36m|\[39m/g, ''); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have we already discussed keeping the styles offline with something like ansi-to-html
?
/** | ||
* Clean ANSI escape codes from text | ||
* @param text The text to clean | ||
* @returns The cleaned text | ||
*/ | ||
export const cleanAnsiCodes = (text: string): string => { | ||
// Split the regex into parts to avoid control characters | ||
const ansiEscapeCodesPattern = new RegExp( | ||
[ | ||
// ESC [ n ; n ; ... m | ||
String.fromCharCode(27) + '\\[\\d+(?:;\\d+)*m', | ||
// Other common ANSI sequences | ||
'\\[2m', | ||
'\\[22m', | ||
'\\[1m', | ||
'\\[36m', | ||
'\\[39m', | ||
].join('|'), | ||
'g', | ||
); | ||
|
||
return text.replace(ansiEscapeCodesPattern, ''); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's functionally different about this cleaner and the one below?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
already resolved in commit 87fa132 (duplicate from trimming down PR)
/** | ||
* Extracts a friendly name from a nested stack logical ID | ||
* @param stackName The stack name to process | ||
* @returns A friendly name or undefined if no match | ||
*/ | ||
const getFriendlyNameFromNestedStackName = ( | ||
stackName: string, | ||
): string | undefined => { | ||
const parts = stackName.split('-'); | ||
|
||
if (parts && parts.length === 7 && parts[3] === 'sandbox') { | ||
return parts[5].slice(0, -10) + ' stack'; | ||
} else if (parts && parts.length === 5 && parts[3] === 'sandbox') { | ||
return 'root stack'; | ||
} | ||
|
||
return undefined; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A good habit when baking arcane splits and magic numbers into a function like this is to show an example or two of the before/after in the docstring. If there is a good name you can give to the magic constants here, give them names. Else, it's helpful to indicate where they come from in the docstring examples.
In this case, the naming patterns might be completely obvious to others in the codebase. But, if someone new (it's always Day 1, right?) jumps in at this point, examples will help.
Leaving this comment on this particularly arcane looking set of manipulations. But, please apply anywhere you feel arcane incantations are being performed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
got it. actually figured out how to actually get non-null metadata which eliminates the need for some of these functions. but examples have been added in the remaining functions where I felt it was needed.
* @param metadata.constructPath Optional construct path from CDK metadata | ||
* @returns A user-friendly name for the resource | ||
*/ | ||
export const createFriendlyName = ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Borderline arcane incantations herein. Partially cp-ing a comment from below. Examples in docstrings will help future maintainers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank-you! Probably goes without saying, but explicit test coverage would also be ideal. Unless I'm overlooking it, just a small set of examples like you have here in a test loop would be good.
cleanedMessage.includes('_IN_PROGRESS') || | ||
cleanedMessage.includes('CREATE_') || | ||
cleanedMessage.includes('DELETE_') || | ||
cleanedMessage.includes('UPDATE_') || | ||
cleanedMessage.includes('Deployment in progress') || | ||
cleanedMessage.includes('COMPLETE') || | ||
cleanedMessage.includes('FAILED') || |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't mind seeing something like this if it speaks to you:
[
'_IN_PROGRESS',
'CREATE_',
'DELETE_',
'UPDATE_',
'Deployment in progress',
'COMPLETE',
'FAILED'
].some(pattern => cleanedMessage.includes(pattern))
But, with room for opinion here, so adopt only if it really speaks to you.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will do.
|
||
// Exit the process if requested | ||
if (exitProcess) { | ||
// Short delay to allow messages to be sent |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What messages? Why is 500 ms
the right amount of time to wait for them to send?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was sending a shutdown log but I guess that isn't super helpful because the process is, well, shutting down. If we want to log, we can always log on socket disconnect. Getting rid of the log and delay. This delay was also for the server close, which is async but doesn't return a promise. resolving that properly with a promise wrapper to make sure server closing is finished before we end the process.
private sandbox: Sandbox, | ||
private getSandboxState: () => Promise<string>, | ||
private backendId: { name: string }, | ||
private shutdownService: import('./shutdown_service.js').ShutdownService, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why import()
ShutdownService
as a function here instead of letting it hang out with its other import
-ant friends?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moving it to the top with the other imports!
socket.on('savedResources', handleSavedResources); | ||
socket.on('deployedBackendResources', handleDeployedBackendResources); | ||
socket.on('customFriendlyNames', handleCustomFriendlyNames); | ||
socket.on('customFriendlyNameUpdated', handleCustomFriendlyNameUpdated); | ||
socket.on('customFriendlyNameRemoved', handleCustomFriendlyNameRemoved); | ||
socket.on('error', handleError); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just picking a random spot to leave this comment: Would be really good to have constants imported from a central spot (at least per group) to match on instead of duplicated strings all over. In theory, a typo in one of those strings could be caught by a good suite of tests as well, but caught much sooner if caught statically.
(Bonus points if you include docstrings to indicate what the constants mean.)
Most IDEs can also help track down usage of a variable/constant more definitively than searching for a string literal.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done. put constants in a shared location that frontend and backend can both access.
if (regionValue.constructor.name === 'AsyncFunction') { | ||
region = await regionValue(); | ||
} else { | ||
region = regionValue(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Q for the team: Do we have lint rules in place that would prevent this from just being await regionValue()
either way? If not, there's usually not much reason to check whether a thing is async. If it can be async, just await
it. (Right?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
resolved by eliminating this code by using RegionFetcher
// case 'AWS::DynamoDB::Table': // COMMENT OUT (UNTESTED) | ||
// // Check if physicalId is an ARN and extract table name if needed | ||
// const dynamoTableName = physicalId.includes(':table/') | ||
// ? physicalId.split(':table/')[1] | ||
// : physicalId; | ||
// return `${baseUrl}/dynamodb/home?region=${region}#/tables/view/${encodeURIComponent(dynamoTableName)}`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You mean not even manually tested? The others aren't covered by unit or integ tests yet, are they?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yep, I did manual testing now though so I've added these back in.
…eploymentProgressLogger and createFriendlyName. maintains backward compatibility
- Introduced SocketClientService to handle socket connections and events. - Created SandboxClientService and ResourceClientService for sandbox and resource-specific socket communication. - Updated App component to utilize context for socket services. - Refactored useResourceManager hook to use ResourceClientService. - Updated ResourceConsole and Header components to use new service structure.
resourceConfigChanged: [ | ||
async () => { | ||
try { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can't find where this event is being emitted by the sandbox
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, its extraneous now that it been replaced with successfulDeployment/failedDeployment for better error handling. removed in PR3 but forgot to propogate back to PR2. Doing that now.
/** | ||
* Gets the current state of the sandbox | ||
* @returns The current state: 'running', 'stopped', 'deploying', 'deleting', 'nonexistent', or 'unknown' | ||
*/ | ||
getState: () => SandboxStatus; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This state management needs more unit test coverage.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Absolutely. In writing my tests I also discovered a tiny edge case error in my state management -- fixed that and added tests.
metadata: | ||
templateMetadata[stackResourceSummary.LogicalResourceId || ''], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be
metadata: | |
templateMetadata[stackResourceSummary.LogicalResourceId || ''], | |
metadata: | |
templateMetadata[stackResourceSummary.LogicalResourceId] || '', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
EDIT: okay, turns out I actually misunderstood this. The reason I had my original code was because in case LogicalResourceId is undefined (which it can be), then we just look for the the metadata of ''. Though I imagine this is not the best way to handle that error. The metadata property should be of type{ constructPath?: string } | undefined
, not string
. I have now re-updated this to the following line, which is more intentional about handling the undefined case - instead of falling back to an empty string key (which likely doesn't exist in the metadata, but maybe in an error case, COULD), it explicitly returns undefined. It avoids a potential issue where templateMetadata[''] might actually contain something unexpected. stackResourceSummary.LogicalResourceId ? templateMetadata[stackResourceSummary.LogicalResourceId] : undefined,
…eployed_resources_enumerator.ts Co-authored-by: Amplifiyer <51211245+Amplifiyer@users.noreply.github.com>
…yanan/amplify-backend into pr-2-backend-services Merge remote changes
packages/cli/src/commands/sandbox/sandbox-devtools/react-app/src/components/Header.tsx
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great job 🚀
case 'debug': | ||
return 'pending'; | ||
default: | ||
return 'info'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why is debug level a pending status and default info?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was just for the UI styling of the little icon that goes next to the level. This is the type:
export declare namespace StatusIndicatorProps { type Type = 'error' | 'warning' | 'success' | 'info' | 'stopped' | 'pending' | 'in-progress' | 'loading'; type Color = 'blue' | 'grey' | 'green' | 'red' | 'yellow'; }
I just thought that the icon for pending fit debug best, and info was most versatile. Is there a choice for either that you think would be better?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
got it. Maybe the name is slightly confusing. Perhaps getStatusIndicatorIconForLogLevel
(verbose but self documenting)
packages/cli/src/commands/sandbox/sandbox-devtools/react-app/src/components/ConsoleViewer.tsx
Outdated
Show resolved
Hide resolved
console.log( | ||
`[CLIENT] Header: sandboxStatus prop changed to ${sandboxStatus}`, | ||
); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we need these log statements?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nope, removing.
if (statusCheckTimeout === 0) { | ||
return ( | ||
<StatusIndicator type="pending"> | ||
Checking Sandbox Status | ||
</StatusIndicator> | ||
); | ||
} else if (statusCheckTimeout === 1) { | ||
return ( | ||
<StatusIndicator type="pending"> | ||
Still checking status... | ||
</StatusIndicator> | ||
); | ||
} else { | ||
return ( | ||
<SpaceBetween direction="horizontal" size="xs"> | ||
<Spinner size="normal" /> | ||
<StatusIndicator type="pending"> | ||
Status check taking longer than expected | ||
</StatusIndicator> | ||
</SpaceBetween> | ||
); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't need this level of messaging. It's either "Waiting for status..." or "${Status}" to keep it simple.
@@ -0,0 +1,116 @@ | |||
/* eslint-disable spellcheck/spell-checker */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In one of future PR, we should remove all file level eslint suppression.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yep, will do.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall LGTM 🚀
e3c1c9b
…rc/components/ConsoleViewer.tsx Co-authored-by: Amplifiyer <51211245+Amplifiyer@users.noreply.github.com>
useEffect(() => { | ||
console.log( | ||
`[CLIENT] Header: sandboxStatus prop changed to ${sandboxStatus}`, | ||
); | ||
|
||
// Always reset loading state when status changes, regardless of the new state | ||
console.log( | ||
`[CLIENT] Header: Resetting isLoading to false due to status change: ${sandboxStatus}`, | ||
); | ||
setIsLoading(false); | ||
|
||
// If status is still unknown after a delay, increment the timeout counter | ||
// to show a more informative message | ||
if (sandboxStatus === 'unknown') { | ||
const timer = setTimeout(() => { | ||
setStatusCheckTimeout((prev) => prev + 1); | ||
}, 3000); | ||
return () => clearTimeout(timer); | ||
} else { | ||
// Reset timeout counter when we get a definitive status | ||
setStatusCheckTimeout(0); | ||
} | ||
}, [sandboxStatus]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can be in a follow up PR but I believe this useEffect can be removed since this component rerenders when sandboxStatus changes (since it is a prop) and you already initialize the state as false
in line 35.
28879e3
into
aws-amplify:feature/dev-tools
Changes
Implements a functional DevTools UI for the Amplify Sandbox, building upon the foundation established in PR1. Key features include:
The implementation uses Express with Socket.IO for the backend server and React with Cloudscape Design components for the frontend UI.
Validation
Unit tests have been included, though some files will only be further tested in later PRs
Checklist
run-e2e
label set.By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.